/**
    ATClab - scriptable Air Traffic Control simulations.
    Copyright (C) 2010 openatclab@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

#ifdef _WIN32
	#include <assert.h>
#endif

#include "canvas.h"

#include "aircraft_agent.h"
#include "aircraftsprite.h"
#include "aircraft_data.h"
#include "canvas_tools.h"

#include "aircraftparam.h"
#include "routearcsprite.h"
#include "scale.h"
#include "sectorarcsprite.h"
#include "sectorsprite.h"
#include "transformation.h"
#include "waypointsprite.h"

#include <algorithm>
#include <cmath>
#include <sstream>
//Added by qt3to4:
#include <Q3PointArray>


using namespace atc;

////////////////////////////////////////////////////////////////////////////////
//
// atc::Canvas
//
//------------------------------------------------------------------------------
// class constants
//

/*!
 * The Chunk Count defines how many blocks to break the canvas into.
 */
const int Canvas::CHUNK_COUNT = 50;

//------------------------------------------------------------------------------
// Constrution/Destruction
//

/*!
 * Default Constructor
 */
Canvas::Canvas(QObject *parent, const char *name)
	  : Q3Canvas   ( parent, name )
	  , _aircraft ()
	  , _2internal( 0 )
	  , _scale    ( 0 )
	  , _brl      ( 0 )
	  , _vector   ( 0 )
	  , _conflictColour()
	  , _palette  ( 0 )
	  , _info()
	  , _infoPosition( TOPLEFT )
{
	setBackgroundColor( SECTOR_BRUSH.color() );
}

/*!
 * Destructor
 */
Canvas::~Canvas() {
	if ( _vector      ) delete _vector;
	if ( _scale       ) delete _scale;

	WayPointVector::const_iterator wp = _waypoints.begin();
	for ( ; wp != _waypoints.end(); ++wp ) 
		delete *wp;
	
	if ( _2internal ) delete _2internal;
	
	ACSpriteMap::const_iterator s = _aircraft.begin();
	for ( ; s != _aircraft.end(); ++s )
		delete s->second;
}

//--------------------------------------------------------------------------------------------------
// [public slots] virtual
//

/*!
 *
 */
void Canvas::add_time_info()  { _info.push_back( new ClockDisplay(this) ); }
void Canvas::add_score_info() { _info.push_back( new ScoreDisplay(this) ); }

void 
Canvas::add_goal_info(	const std::string& a_label,
						const std::string& a_globalRef,
						const std::string& a_text)  
{ 
	_info.push_back( new GoalDisplay(this, a_label, a_globalRef, a_text)); 
}

void Canvas::add_tool_info()  { _info.push_back( new ToolDisplay(this) ); }

void 
Canvas::AddSelfReferentGoalInfo()
{
	_info.push_back(new SelfReferentGoalDisplay(this));
}

void 
Canvas::AddNormativeGoalInfo()
{
	_info.push_back(new NormativeGoalDisplay(this));
}

void
Canvas::AddCorrectDecisionsInfo(const std::string& a_label,
								const std::string& a_globalRef,
								const std::string& a_text)
{
	_info.push_back(new CorrectDecisionsDisplay(this, a_label, a_globalRef, a_text));
}

void
Canvas::AddIncorrectDecisionsInfo(	const std::string& a_label,
									const std::string& a_globalRef,
									const std::string& a_text)
{
	_info.push_back(new IncorrectDecisionsDisplay(this, a_label, a_globalRef, a_text));
}

/*!
 *
 */
void Canvas::show_info() {

	int margin = 10;
	QPoint origin( margin, margin );
	QPoint offset( 0, 0 );

	int display_width = width();
	int display_height = height();

	InfoBlockIt blkIt = _info.begin();

	int block_width = (*blkIt)->width() + margin;
	int block_height = (*blkIt)->height() + margin;

	int block_count = _info.size();

	switch (_infoPosition) {
	case TOPLEFT:	
		offset.setY( block_height );		
		break;
	case TOP:
		origin.setX( ( display_width - (block_width * block_count - margin) ) / 2 );
		offset.setX( block_width );
		break;
	case TOPRIGHT:
		origin.setX( display_width - block_width );
		offset.setY( block_height );		
		break;
	case LEFT:
		origin.setY( ( display_height - (block_height * block_count - margin) ) / 2 );
		offset.setY( block_height );		
		break;
	case CENTER:
		origin.setX( ( display_width - (block_width * block_count - margin) ) / 2 );
		origin.setY( ( display_height - (block_height - margin) ) /2 );
		offset.setX( block_width );
		break;
	case RIGHT:
		origin.setX( display_width - block_width );
		origin.setY( ( display_height - (block_height * block_count - margin) ) / 2 );
		offset.setY( block_height );		
		break;
	case BOTTOMLEFT:
		origin.setY( display_height - block_height * block_count );
		offset.setY( block_height );		
		break;
	case BOTTOM:
		origin.setX( ( display_width - (block_width * block_count - margin) ) / 2 );
		origin.setY( display_height - block_height );
		offset.setX( block_width );
		break;
	case BOTTOMRIGHT:
		origin.setX( display_width - block_width );
		origin.setY( display_height - block_height * block_count );
		offset.setY( block_height );		
		break;
	}

	QPoint insert( origin );
	for ( ; blkIt != _info.end(); ++blkIt ) {
		(*blkIt)->move( insert );
		(*blkIt)->setVisible( true );
		
		switch (_infoPosition) 
		{
		case TOPLEFT:	
			offset.setY((*blkIt)->height() + margin);		
			break;
		case TOP:
			offset.setX((*blkIt)->width() + margin);
			break;
		case TOPRIGHT:
			offset.setY((*blkIt)->height() + margin);		
			break;
		case LEFT:
			offset.setY((*blkIt)->height() + margin);		
			break;
		case CENTER:
			offset.setX((*blkIt)->width() + margin);
			break;
		case RIGHT:
			offset.setY((*blkIt)->height() + margin);		
			break;
		case BOTTOMLEFT:
			offset.setY((*blkIt)->height() + margin);		
			break;
		case BOTTOM:
			offset.setX((*blkIt)->width() + margin);
			break;
		case BOTTOMRIGHT:
			offset.setY((*blkIt)->height() + margin);		
			break;
		}

		insert += offset;
	}
}


/*!
 * Display the update counter with a specific format string.
void Canvas::show_clock( ) {
	_clock     = new QCanvasText( "--:--:--",   CANVAS_FONT, this );
	_tool_name = new QCanvasText( " ---      ", CANVAS_FONT, this );

	QRect cr( _clock->boundingRect() );
	QRect tr( _tool_name->boundingRect() );
	int xcoord = this->width() / 2;
	int ycoord = this->height() - 2 * cr.height();

	_clock->setColor( Qt::black );
	_clock->move( xcoord - cr.width(), ycoord );
	_clock->setVisible( true );

	_tool_name->setColor( Qt::blue );
	_tool_name->move( xcoord + 10, ycoord );
	_tool_name->setVisible( true );

	QCanvasRectangle *rc = new QCanvasRectangle( _clock->boundingRect(), this );
	rc->setPen( QPen( Qt::black, 2 ) );
	rc->show();

	QCanvasRectangle *rn = new QCanvasRectangle( _tool_name->boundingRect(), this );
	rn->setPen( QPen( Qt::black, 2 ) );
	rn->show();
}
 */

//------------------------------------------------------------------------------
// [public slots] virtual
//

/*!
 * set tool
 */
void Canvas::set_tool( QString name ) {
	emit sig_update_tool( name );
}

//------------------------------------------------------------------------------
// [public] canvas tools interface
//

//
// Bearing Range Line
//

/*!
 *
 */
BearingRangeLine* Canvas::add_brl( int x, int y ) {
	// @todo:
	_brl = new BearingRangeLine( this );
	_brls.push_back( _brl );
	_brl->anchor( QPoint( x, y ) );
	return _brl;
}

/*!
 *
 */
BearingRangeLine* Canvas::add_brl( AircraftSprite *s ) {
	// @todo:
	_brl = new BearingRangeLine( this );
	_brls.push_back( _brl );
	_brl->anchor( s );
	return _brl;
}

/*!
 *
 */
BearingRangeLine* Canvas::add_brl( WayPointSprite *s ) {
	_brl = new BearingRangeLine( this );
	_brls.push_back( _brl );
	_brl->anchor( s );
	return _brl;
}

/*!
 *
 */
void Canvas::hook_brl( int x, int y ) {
	_brl->hook( QPoint( x, y ) );
}

/*!
 *
 */
void Canvas::hook_brl( AircraftSprite *s ) {
	_brl->hook( s );
}

/*!
 *
 */
void Canvas::hook_brl( WayPointSprite *s ) {
	_brl->hook( s );
}

/*!
 *
 */
void Canvas::select_brl( BearingRangeLine *brl ) {
	_brl = brl;
}

/*!
 *
 */
void Canvas::remove_brl( BearingRangeLine *brl ) {
	_brls.remove( brl );
	delete( brl );
	update();
}

//
// Scale
//

/*!
 * Add a scale indicator to the canvas
 */
void Canvas::add_scale(
	int x, int y, int xextent, int yextent, int interval, int tick_size
){
	_add_scale( xextent, yextent, interval, tick_size );
	_scale->move_by( to_internal_length( x ), to_internal_length( y ) );
}

/*!
 *
 */
void Canvas::add_scale( 
	   int xextent, int yextent, int interval, int tick_size, 
	   GridPosition position 
) {
	_add_scale( xextent, yextent, interval, tick_size );

	int margin = 50;
	int xpos = to_internal_length( xextent ) + margin;
	int ypos = to_internal_length( yextent ) + margin;

	QPoint insert( xpos, ypos );

	switch (position) {
	case TOPLEFT:
		break;
	case TOP:
		insert.setX( width() / 2 );
		break;
	case TOPRIGHT:
		insert.setX( width() - xpos );
		break;
	case LEFT:
		insert.setY( height() / 2 );
		break;
	case CENTER:
		insert.setX( width() / 2 );
		insert.setY( height() / 2 );
		break;
	case RIGHT:
		insert.setX( width() - xpos );
		insert.setY( height() / 2 );
		break;
	case BOTTOMLEFT:
		insert.setY( height() - ypos );
		break;
	case BOTTOM:
		insert.setX( width() / 2 );
		insert.setY( height() - ypos );
		break;
	case BOTTOMRIGHT:
		insert.setX( width() - xpos );
		insert.setY( height() - ypos );
		break;
	};
	_scale->move_by( insert );
}

/*!
 * Add a scale indicator to the canvas at the origin ( probably patially offscreen ).
 */
void Canvas::_add_scale( int xextent, int yextent, int interval, int tick_size ) {
	_scale = new ScaleXHair( 
			this
		  , to_internal_length( xextent ), to_internal_length( yextent )
		  , to_internal_length( interval ), to_internal_length( tick_size ) 
	);
}

/*!
 *
 */
void Canvas::move_scale( QPoint pt ) {
	_scale->move_by( pt.x(), pt.y() );
}

//
// vector
//

/*!
 *
 */
VectorTool* Canvas::vector_tool() { return _vector; }

/*!
 *
 */
VectorTool* Canvas::add_vector( AircraftSprite *s ) {
	_vector = new VectorTool( this, s );
	return _vector;
}

/*!
 *
 */
void Canvas::move_vector( QPoint pt ) { _vector->set_end( pt.x(), pt.y() ); }

/*!
 *
 */
void Canvas::cancel_vector() {
	delete _vector;
	_vector = 0;
}


/*!
 *
 */
void Canvas::toggle_srprobe( int count ) {
	ACSpriteMap::iterator ac =  _aircraft.begin();
	for ( ; ac != _aircraft.end(); ++ac ) {
		for ( int i = 0; i < count; ++i ) { 
			ac->second->toggle_probe();
		}
	}
}

//
//
//

/*!
 *
 */
void Canvas::set_conflict_colour( Colour c ) { _conflictColour = c; }

/*!
 *
 */
QColor Canvas::conflict_colour() { return _conflictColour.colour; }

/*!
 *
 */
bool Canvas::conflict_blink() {	return _conflictColour.blink; }

/*!
 *
 */
void Canvas::set_palette( Palette* p ) { _palette = p; }

/*!
 *
 */
QColor Canvas::colour_lookup( ControlState s ) { return (*_palette)[ s ].colour; }

/*!
 *
 */
bool Canvas::blink_lookup( ControlState s ) { return (*_palette)[ s ].blink; }


//--------------------------------------------------------------------------------------------------
// [public] transformation interface
//

/*!
 *
 */
double Canvas::to_internal_length( double len ) const {
	return _2internal->length( len );
}

/*!
 *
 */
std::pair< double, double > 
Canvas::to_internal_point( const double x, const double y ) const {
	return _2internal->point( x, y );
}

/*!
 *
 */
QPoint 
Canvas::to_internal_qpoint( const double x, const double y ) const {
	return _2internal->qpoint( x, y );
}

/*!
 *
 */
double Canvas::to_user_length( double len ) const {
	return _2internal->inverse_length( len );
}

/*!
 *
 */
std::pair< double, double > 
Canvas::to_user_point( const double x, const double y ) const {
	return _2internal->inverse_point( x, y );
}

/*!
 *
 */
QPoint 
Canvas::to_user_qpoint( const double x, const double y ) const {
	return _2internal->inverse_qpoint( x, y );
}

//------------------------------------------------------------------------------
// [public] initialization (map objects - integer)
//
/*!
 *
 */
void Canvas::add_active_sector( const AreaDefinition &def ) {
	Q3PointArray pts;
	build_sector( new ActiveSectorSprite( this ), area_points( def, pts ) );
}


/*!
 *
 */
void Canvas::add_sector( const AreaDefinition &def ) {
	Q3PointArray pts;
	build_sector( new SectorSprite( this ), area_points( def, pts ) );
}

/*!
 * add a weather system
 */
void Canvas::add_weather( const AreaDefinition& def ) {
	Q3PointArray pts;
	area_points( def, pts );

	Q3CanvasPolygon *poly = new Q3CanvasPolygon( this );
	poly->setPoints( pts );
	poly->setBrush( WEATHER_BRUSH );
	poly->setZ( WEATHER_Z );
	poly->show();
}

/*!
 */
void Canvas::add_waypoint( const std::string& name, double x, double y ) {
	_waypoints.push_back( 
		new WayPointSprite( name, _2internal->qpoint( x, y ), this ) 
	);
}

/*!
 */
void Canvas::add_route( const RouteList &pts ) {
	if ( 2 > pts.size() ) return;

	RouteList::const_iterator pt = pts.begin();
	QPoint p0 = _2internal->qpoint( pt->x, pt->y );
	QPoint p1;
	++pt;
	for( ; pt != pts.end(); ++pt ) {
		p1 = _2internal->qpoint( pt->x, pt->y );

		RouteArcSprite *s = new RouteArcSprite( this );
		s->setPoints( p0.x(), p0.y(), p1.x(), p1.y() );
		s->show();

		p0 = p1;
	}
}


//------------------------------------------------------------------------------
// [public] initialization (sky objects - double precision)
//

/*!
 * Add an aircraft to the canvas
 *
 * Creates a new aircraft sprite which is a controller for the aircraft agent.
 * A list of aircraft sprites is maintained for update purposes.
 */
void Canvas::add_aircraft( AircraftAgent *ac ) {
	AircraftSprite *s = new AircraftSprite( this, ac );
	_aircraft.insert( std::make_pair( ac->callsign(), s ) );

	s->set_visible( true );

	connect( ac, SIGNAL( updated()     ), s, SLOT( invalidate()    ) );
	connect( ac, SIGNAL( waypoint()    ), s, SLOT( next_waypoint() ) );
	connect( ac, SIGNAL( deactivated() ), s, SLOT( destroy()       ) );
	
	connect( ac, SIGNAL( activated() ), s, SLOT( update() ) );
}


//------------------------------------------------------------------------------
// public [slots]
//

/*!
 * Update all aircraft
 */
void Canvas::update_aircraft() {
	ACSpriteMap::iterator ac =  _aircraft.begin();
	for ( ; ac != _aircraft.end(); ++ac ) {
		ac->second->update();
	}

	BRLList::iterator brl = _brls.begin();
	for ( ; brl != _brls.end(); ++brl ) {
		(*brl)->update();
	}

	if ( _vector )
		_vector->update();

	update();
}


//------------------------------------------------------------------------------
// public: [virtual]
//

/*!
 * Resize and Retune the Canvas from zero sized initial state.
 */
void Canvas::resize( const QRect &map ) {
	if ( _2internal )
		throw canvas_error( "canvas previously initialized" );
		
	_2internal = new Transformation();
	QRect r = _2internal->calculate( map, CANVAS_SIZE_HINT );

	// automatically retune on resize 
	// aiming for approx CHUNK_COUNT chunks.

	int w = r.width();
	int h = r.height();

	int dim = (w > h ? w : h);
	int ln2 = int( log((double)(dim / CHUNK_COUNT)) / log(2.0) ) + 1;
	int chksz = (int)pow(2.0, ln2);
	retune(chksz);	// quick since no resize yet.

	Q3Canvas::resize(w, h);

	if ( !_info.empty() ) show_info();
}


//------------------------------------------------------------------------------
// [private]
//

/*!
 *
 */
void Canvas::build_sector( SectorSprite *ss, const Q3PointArray &points ) {
	//
	// create sector
	//

	ss->setPoints( points );
	ss->show();

	//
	// process the canvas points to create sector outline
	//

	QPoint p1( points.at( points.size() - 1 ) );
	for ( int i = 0; i < points.size(); ++i ) {
		
		QPoint p2( points[i] );
		
		SectorArcSprite *sas = new SectorArcSprite( this );
		sas->setPoints( p1.x(), p1.y(), p2.x(), p2.y() );
		sas->show();

		p1 = p2;
	}
}

/*!
 *
 */
Q3PointArray& Canvas::area_points( 
		const AreaDefinition &area_def
	  , Q3PointArray &pts_ref 
) {
	// validate point array
	if ( ! pts_ref.isNull() ) {
		throw canvas_error( "Cannot process area: point array is not empty!" );
	}
	
	// process area's descriptors
	AreaDefinition::const_iterator it = area_def.begin();
	for ( ; it != area_def.end(); ++it) {

		switch ( (*it)->rtti ) {
		
		case ( AreaDescriptor::RTTI_VERTEX ): {
			QPoint p( _2internal->qpoint( (*it)->x, (*it)->y ) );
			pts_ref.putPoints( pts_ref.size(), 1, p.x(), p.y() ); 
			break;
		}
		case ( AreaDescriptor::RTTI_ARC ): {
			ArcDescriptor *arc = static_cast< ArcDescriptor* >( *it );

			QPoint p0 = _2internal->qpoint( arc->x, arc->y );
			int    r  = (int)( _2internal->length( arc->r ) );

			if ( 0 == pts_ref.size() ) {
				// assume 360 single area arc descriptor
				// @todo: assert( only area descriptor )
				pts_ref.makeEllipse( p0.x() - r, p0.y() - r , 2 * r, 2 * r );
			}
			else {
				++it;  // need next point also to limit arc

				assert( it != area_def.end() );

				QPoint pA( pts_ref.at( pts_ref.size() - 1 ) );
				QPoint pB( (*it)->x, (*it)->y );

				bool minor = true;  // TODO:??

				double bA = bearing(p0, pA);
				double bB = bearing(p0, pB);

				if ( (bB - bA > 180 && minor) || (bB - bA < 180 && !minor) ) {
					double tmp = bA;
					bA = bB;
					bB = tmp;
				}

				double angle = bB - bA; 

				Q3PointArray arcPts;
				arcPts.makeArc(
					p0.x() - r, p0.y() - r, 2 * r, 2 * r, 
					(floor)(bA * 16 + 0.5), (floor)(angle * 16 + 0.5)
				);

				// Note: do not include pA or pB in final pts.
				pts_ref.putPoints( pts_ref.size() - 1, arcPts.size(), arcPts );
			}			
			
			break;
		}
		case ( AreaDescriptor::RTTI_ELLIPSE ): {
			EllipseDescriptor *e 
				  = static_cast< EllipseDescriptor* >( *it );

			QPoint p0 = _2internal->qpoint( e->x, e->y );
			int    w  = (int)( _2internal->length( e->w ) );
			int    h  = (int)( _2internal->length( e->h ) );
			int    a  = ( e->a );

			// @todo: assert only one area descriptor (this)
			pts_ref.makeEllipse( p0.x() - w/2, p0.y() - h/2 , w, h );

			if ( a ) {
				QMatrix matrix;
				matrix.rotate( -a );
				pts_ref.translate( -p0.x(), -p0.y() );
				pts_ref = matrix.map( pts_ref );
				pts_ref.translate(  p0.x(),  p0.y() );
			}
			
			break;
		}
		default:
			throw canvas_error("Unknown area descriptor!");
		}
	}
	return pts_ref;
}

/*!
 * Calculate the bearing from _*canvas*_ point p1 to point p2 (degrees)
 *
 * The points are already converted to canvas space which has an upside down
 * geometry ie +ve y is down. Bearings are still however measured in clockwize
 * rotation from the y-axis;
 * 
 */
double Canvas::bearing(QPoint p1, QPoint p2)const 
{
	double dy = p2.y() - p1.y();
	double dx = p2.x() - p1.x();

	double a = 
		PI - 
		(
			dx != 0 
			? atan(dy / dx) 
			: (dy < 0 ? - PI/2 : PI/2) 
		);

	if ( dx >= 0 ) a += PI * ( dy < 0 ? -1 : 1);
	// slightly confusing because canvas space (like screen) is upside down

	return a * 180 / PI;
}


////////////////////////////////////////////////////////////////////////////////
